package overall

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	. "go_tempate/SQL/redis"
	. "go_tempate/common/function"
	. "go_tempate/common/store"
	. "go_tempate/common/struct"
	. "go_tempate/config"
	"net/http"
	"strconv"
)

/*
 登录检查拦截中间件

 作用：
  1.拦截非 不需要登录的白名单 NonLoginWhitelist 的接口，进行判断是否带有token头部
  2.判断用户token是否过期+长效token是否过期，都过期则提醒重新登陆，但若是长效token还存在，则仅仅是更新一个新的用户token即可
  3.根据上下文绑定的响应结构体返回状态 IsReturn ，如果true则按照默认进行JOSN格式返回，否则则是按照自己定义返回

 绑定上下文：
  1.userInfo: 仅仅在token成功解析情况下才会出现在上下文中，内容是当前的用户ID
  2.isLoginWhite： 是否是不需登录白名单成员
  3.ResponseBody: 响应请求体(后续路由操作，只需要修改此结构体数据则可实现默认JSON自动返回)
*/
// ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
func UserLogin() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		// 获取当前请求的路径
		requestPath := ctx.Request.URL.Path

		//判断是否在不需要登录的白名单中
		isLoginWhite := NonLoginWhitelist[requestPath]

		//获取头部中 AccessToken
		AccessToken := ctx.GetHeader("AccessToken")

		//声明后续接口响应体
		ResponseBody := DefaultResponseBody()

		//如果当前请求路径不在 不需要登录白名单中，且也没有 AccessToken
		//----------------------------------------------------------------------------------------------------------
		if !isLoginWhite && AccessToken == "" {
			//设置响应体内容
			ResponseBody.BatchSettings(LoginErr, NotLoginOrExpiredLogin, nil)

			//中断请求，并返回未登录的响应体
			ctx.AbortWithStatusJSON(http.StatusUnauthorized, ResponseBody.TransformGINH())
			return
		}

		//当前请求路径是需要登录的话
		//----------------------------------------------------------------------------------------------------------
		if !isLoginWhite {
			//解析当前 AccessToken
			userInfo, ok := JwtParentToken(AccessToken)

			//创建后续进行redis操作的查询变量
			useId := strconv.Itoa(int(userInfo.UserID))

			//获取在冻结列表中，是否有此用户
			//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
			if freezeStatus, err := GetRedisItem(FreezeName + useId); err != nil {
				//如果查询失败
				ResponseBody.BatchSettings(GetUserInfoErr, UseRedisError, nil)

				//中断请求，并返回未登录的响应体
				ctx.AbortWithStatusJSON(http.StatusUnauthorized, ResponseBody.TransformGINH())
				return

				//当查询结果为冻结状态时
			} else if freezeStatus == "3" {
				//修改响应体内容
				ResponseBody.Message = "此用户已冻结！！"

				//中断请求，并返回未登录的响应体
				ctx.AbortWithStatusJSON(LoginErr, ResponseBody.TransformGINH())
				return
			}

			//从redis中获取当前用户下的token值
			//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
			redisToken, err := GetRedisItem(useId)

			//如果 redis 中的数据过期
			if err != nil && err.Error() == "密钥已过期" && ok >= 0 {
				//修改响应体内容
				ResponseBody.BatchSettings(LoginErr, JwtErr[ok], nil)

				//中断请求，并返回未登录的响应体
				ctx.AbortWithStatusJSON(http.StatusUnauthorized, ResponseBody.TransformGINH())
				return

			} else if err != nil {
				//修改响应体内容
				ResponseBody.BatchSettings(LoginErr, GetTokenInRedisError, nil)

				//中断请求，并返回未登录的响应体
				ctx.AbortWithStatusJSON(http.StatusUnauthorized, ResponseBody.TransformGINH())
				return
			}

			//如果此时的 token 与缓存中的不一致
			if redisToken != AccessToken {
				//设置响应体内容
				ResponseBody.BatchSettings(LoginErr, NotLoginOrExpiredLogin, nil)

				//中断请求，并返回未登录的响应体
				ctx.AbortWithStatusJSON(http.StatusUnauthorized, ResponseBody.TransformGINH())
				return
			}

			//根据当前返回的解析token状态进行对应操作
			switch ok {
			//AccessToken未过期且解析成功
			//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
			case -1:
				//如果一致则进行设置，绑定当前用户信息，userInfo
				ctx.Set("userInfo", userInfo)

			//AccessToken过期
			//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
			case 1:
				//redis没有过期的话，重新创建一个 AccessToken
				newToken, err := JwtCreateToken(DefaultCustomClaims(userInfo.UserID, AccessTokenEffectiveTime))

				//如果创建发生错误
				if err != nil {
					//修改响应体内容
					ResponseBody.BatchSettings(LoginErr, TokenGenerationFailed, nil)

					//中断请求，并返回未登录的响应体
					ctx.AbortWithStatusJSON(http.StatusUnauthorized, ResponseBody.TransformGINH())
					return
				}

				//创建成功后重新更新redis中的缓存数据，且延迟期限
				err = SetRedisItems(useId, newToken, RefreshEffectiveTime)

				if err != nil {
					//修改响应体内容
					ResponseBody.BatchSettings(LoginErr, TokenJoinRedisError, nil)

					//中断请求，并返回未登录的响应体
					ctx.AbortWithStatusJSON(http.StatusUnauthorized, ResponseBody.TransformGINH())
					return
				}

				ctx.Set("userInfo", userInfo) // 绑定当前用户信息，userInfo
				ResponseBody.Token = newToken // 在响应体中绑定新的用户 token

			//其余报错的情况
			//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
			default:
				//修改响应体内容
				ResponseBody.BatchSettings(LoginErr, JwtErr[ok], nil)

				//中断请求，并返回未登录的响应体
				ctx.AbortWithStatusJSON(http.StatusUnauthorized, ResponseBody.TransformGINH())
				return
			}
		}

		//如果此接口满足不需登录白名单或存在token头部则绑定数据到gin回话体上
		ctx.Set("isLoginWhite", isLoginWhite) // 是否是不需登录白名单成员
		ctx.Set("ResponseBody", ResponseBody) // 响应请求体

		//继续后续函数
		// ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		ctx.Next()

		//如果返回响应体中开启了默认返回格式则对此时的数据进行返回
		//----------------------------------------------------------------------------------------------------------
		if ResponseBody.IsReturn {
			//判断如果出现错误状态
			if ResponseBody.Code >= Error {
				//打印错误日志
				zap.S().Errorf("%v请求错误内容:%v", requestPath, ResponseBody.Message)
			}

			//进行公共默认处理
			ctx.JSON(ResponseBody.Status, ResponseBody.TransformGINH())
		}
	}
}
